#include <types.h>
#include <malloc.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/portcomm.h>
#include <sys/lpc.h>

lpc_parcel_t lpc_parcel_get()
{
    lpc_parcel_t parcel = malloc(PORT_MSG_SIZE);

    if (parcel)
    {
        memset(parcel, 0, PORT_MSG_SIZE);
    }
    return parcel;
}

int lpc_parcel_put(lpc_parcel_t parcel)
{
    if (parcel)
    {
        free(parcel);
        return 0;
    }
    return -1;
}

static uint16_t lpc_parcel_get_arg_type(lpc_parcel_t parcel, uint16_t idx)
{
    return parcel->header.argtype[idx];
}

static int lpc_parcel_alloc_arg_solt(lpc_parcel_t parcel)
{
    int i;

    if (!parcel)
        return -1;
    for (i = 0; i < LPC_PARCEL_ARG_NUM; i++)
    {
        if (!(parcel->header.argused & (1 << i)))
            break;
    }
    if (i >= LPC_PARCEL_ARG_NUM)
        return -1;
    return i;
}

static int lpc_parcel_find_arg_solt(lpc_parcel_t parcel, uint16_t type)
{
     int i;
    if (!parcel)
        return -1;
    for (i = 0; i < LPC_PARCEL_ARG_NUM; i++)
    {
        if (parcel->header.argused & (1 << i))
                if ((parcel->header.argtype[i] == type) && (parcel->header.arglen[i] >= 0))
                    break;
    }
    if (i >= LPC_PARCEL_ARG_NUM)
        return -1;
    return i;
}

static void lpc_parcel_set_arg(lpc_parcel_t parcel, uint16_t idx, uint32_t data, uint16_t len, uint16_t type)
{
    parcel->header.args[idx] = data;
    parcel->header.arglen[idx] = len;
    parcel->header.argtype[idx] = type;
    parcel->header.argused |= (1 << idx);
}

static void lpc_parcel_clear_arg(lpc_parcel_t parcel, uint16_t idx)
{
    parcel->header.args[idx] = 0;
    parcel->header.arglen[idx] = 0;
    parcel->header.argtype[idx] = LPC_PARCEL_ARG_UNKNOW;
    parcel->header.argused &= ~(1 << idx);
}

void lpc_parcel_dump_args(lpc_parcel_t parcel)
{
    int i, j;
    printf("parcel args:\n");
    for (i = 0; i < LPC_PARCEL_ARG_NUM; i++)
    {
        if (parcel->header.argused & (1 << i))
        {
            printf("arg%d:", i);
            switch (lpc_parcel_get_arg_type(parcel, i))
            {
            case LPC_PARCEL_ARG_SEQUEUECE:
            {
                printf("seq[%d]", parcel->header.arglen[i]);
                for (j = 0; j > parcel->header.arglen[i]; j++)
                {
                    printf("%d", parcel->data[parcel->header.args[i] + j]);
                }
                printf("\n");
            }
            break;
            case LPC_PARCEL_ARG_STRING:
            {
                printf("str[%d] %s\n", parcel->header.arglen[i], &parcel->data[parcel->header.args[i]]);
            }
            break;
            case LPC_PARCEL_ARG_INT:
            {
                printf("int[%d] %d\n", parcel->header.arglen[i], parcel->header.args[i]);
            }
            break;
            default:
                printf("unknow[%d] %d\n", parcel->header.arglen[i], parcel->header.args[i]);
                break;
            }
        }
    }
}

int lpc_parcel_write_string(lpc_parcel_t parcel, char *str)
{
    int i = lpc_parcel_alloc_arg_solt(parcel);
    int len;
    int size;
    char *buff;
    if (i < 0)
        return -1;
    len = strlen(str);
    size = parcel->header.size + len + 1;
    if (size >= LPC_PARCEL_BUFF_SIZE)
        return -1;
    lpc_parcel_set_arg(parcel, i, parcel->header.size, len+1, LPC_PARCEL_ARG_STRING);
    buff = &parcel->data[parcel->header.size];
    if (str)
        memcpy(buff, str, len);
    buff[len] = '\0';
    parcel->header.size = size;
    return 0;
}

int lpc_parcel_write_sequence(lpc_parcel_t parcel, void *buff, size_t len)
{
    int i = lpc_parcel_alloc_arg_solt(parcel);
    int size;
    uint8_t *pbuff;
    if (i < 0)
        return -1;
    size = parcel->header.size + len+1 ;
    if (size >= LPC_PARCEL_BUFF_SIZE)
        return -1;
    lpc_parcel_set_arg(parcel, i, parcel->header.size, len, LPC_PARCEL_ARG_SEQUEUECE);
    pbuff = &parcel->data[parcel->header.size];
    if (buff)
        memcpy(pbuff, buff, len);
    pbuff[len] = '\0';
    parcel->header.size = size;
    return 0;
}

int lpc_parcel_write_int(lpc_parcel_t parcel, uint32_t num)
{
    int i = lpc_parcel_alloc_arg_solt(parcel);
    if (i < 0)
        return -1;
    lpc_parcel_set_arg(parcel, i, num, sizeof(num), LPC_PARCEL_ARG_INT);
    return 0;
}

int lpc_parcel_write_short(lpc_parcel_t parcel, uint16_t num)
{
    int i = lpc_parcel_alloc_arg_solt(parcel);
    if (i < 0)
        return -1;
    lpc_parcel_set_arg(parcel, i, num, sizeof(num), LPC_PARCEL_ARG_SHORT);
    return 0;
}

int lpc_parcel_write_long(lpc_parcel_t parcel, uint64_t num)
{
    int i = lpc_parcel_alloc_arg_solt(parcel);
    if (i < 0)
        return -1;
    lpc_parcel_set_arg(parcel, i, num, sizeof(num), LPC_PARCEL_ARG_LONG);
    return 0;
}

int lpc_parcel_write_char(lpc_parcel_t parcel, uint8_t ch)
{
    int i = lpc_parcel_alloc_arg_solt(parcel);
    if (i < 0)
        return -1;
    lpc_parcel_set_arg(parcel, i, ch, sizeof(ch), LPC_PARCEL_ARG_CHAR);
    return 0;
}

int lpc_parcel_read_char(lpc_parcel_t parcel, uint8_t *ch)
{
    int i = lpc_parcel_find_arg_solt(parcel, LPC_PARCEL_ARG_CHAR);
    if (i < 0)
        return -1;
    if (ch)
        *ch = parcel->header.args[i];
    lpc_parcel_clear_arg(parcel, i);
    return 0;
}

int lpc_parcel_read_int(lpc_parcel_t parcel, uint32_t *num)
{
    int i = lpc_parcel_find_arg_solt(parcel, LPC_PARCEL_ARG_INT);
    if (i < 0)
        return -1;
    if (num)
        *num = parcel->header.args[i];
    lpc_parcel_clear_arg(parcel, i);
    return 0;
}

int lpc_parcel_read_string(lpc_parcel_t parcel, char **str)
{
    int i = lpc_parcel_find_arg_solt(parcel, LPC_PARCEL_ARG_STRING);
    int len;
    if (i < 0)
        return -1;
    len = parcel->header.arglen[i];
    if (str)
    {
        *str = &parcel->data[parcel->header.args[i]];
    }
    lpc_parcel_clear_arg(parcel, i);
    return 0;
}

int lpc_parcel_read_sequence_buff(lpc_parcel_t parcel, void **buff, size_t *len)
{
    int i = lpc_parcel_find_arg_solt(parcel, LPC_PARCEL_ARG_SEQUEUECE);
    if (i < 0)
        return -1;
    if (buff)
        *buff = &parcel->data[parcel->header.args[i]];
    if (len)
        *len = parcel->header.arglen[i];
}

int lpc_do_echo(uint32_t port, lpc_handler_t func)
{
    bool result = false;
    lpc_parcel_t recv_parcel;
    lpc_parcel_t reply_parcel;

#if LPC_MSG_USE_MALLO == 1
    port_msg_t *msg_recv = NULL;
    port_msg_t *msg_reply = NULL;

    msg_recv = malloc(sizeof(port_msg_t));
    if (msg_recv)
    {
        fprintf(stderr, "lpc: malloc for recv msg failed!\n");
        return -1;
    }
    msg_reply = malloc(sizeof(port_msg_t));
    if (msg_reply)
    {
        fprintf(stderr, "lpc: malloc for reply msg failed!\n");
        free(msg_reply);
        return -1;
    }
#else
    port_msg_t msg_recv_buff;
    port_msg_t msg_reply_buff;
    memset(&msg_recv_buff, 0, sizeof(port_msg_t));
    memset(&msg_reply_buff, 0, sizeof(port_msg_t));
    port_msg_t *msg_recv = &msg_recv_buff;
    port_msg_t *msg_reply = &msg_reply_buff;
#endif
    while (1)
    {
        port_msg_reset(msg_recv);
        port_msg_reset(msg_reply);
        //recv msg from port
        if (receive_port(port, msg_recv) < 0)
            continue;
        recv_parcel = (lpc_parcel_t)msg_recv->data;
        if (func) //callback func
        {
            result = func(recv_parcel->code, recv_parcel, reply_parcel);
        }
        if (!result)
        {
            fprintf(stderr, "lpc: port %d do serv func failed!\n", port);
        }
        //make reply msg
        port_msg_copy_header(msg_reply, msg_recv);
        msg_reply->header.size = sizeof(_lpc_parcel_t) + reply_parcel->header.size;
        msg_reply->header.size += sizeof(port_msg_header_t);
        //reply port msg
        if (reply_port(port, msg_reply) < 0)
        {
            fprintf(stderr, "lpc: reply port %d failed!\n", port);
        }
    }
}

int lpc_echo(uint32_t port, lpc_handler_t func)
{
    if (bind_port(port, 0) < 0)
    {
        fprintf(stderr, "lpc: bind port %d failed!\n", port);
        return -1;
    }
    return lpc_do_echo(port, func);
}

int lpc_echo_group(uint32_t port, lpc_handler_t func)
{
    if (bind_port(port, PORT_COM_GRAPH) < 0)
    {
        fprintf(stderr, "lpc: bind port group %d failed!\n");
        return -1;
    }
    return lpc_do_echo(port, func);
}

int lpc_call(uint32_t port, uint32_t code, lpc_parcel_t data, lpc_parcel_t reply)
{
    int msglen;
//alloc msg buffer
#if LPC_MSG_USE_MALLOC == 1
    port_msg_t *msg = (port_msg_t *)malloc(sizeof(port_msg_t));
    if (!msg)
        return -1;
#else
    port_msg_t msg_buff;
    memset(msg_buff, 0, sizeof(port_msg_t));
    port_msg_t *msg = msg_buff;
#endif

    //bind port
    if (bind_port(-1, PORT_BIND_ONCE) < 0)
    {
#if LPC_MSG_USE_MALLOC == 1
        free(msg);
#endif
        return -1;
    }
    port_msg_reset(msg);
    data->code = code;
    msglen = sizeof(_lpc_parcel_t) + data->header.size;
    //make msg
    memcpy(msg->data, data, msglen);
    msg->header.size = sizeof(port_msg_header_t);
    msg->header.size += msglen;
    //send request
    if (request_port(port, msg) < 0)
    {
#if LPC_MSG_USE_MALLOC == 1
        free(msg);
#endif
        return -1;
    }
    //get reply data
    if (reply)
    {
        lpc_parcel_t _reply = (lpc_parcel_t)msg->data;
        memcpy(reply, msg->data, sizeof(_lpc_parcel_t) + _reply->header.size);
    }
#if LPC_MSG_USE_MALLOC == 1
    free(msg);
#endif
    return 0;
}
